1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.apache.tapestry5.internal.services;
16
17 import org.apache.tapestry5.ComponentResources;
18 import org.apache.tapestry5.Field;
19 import org.apache.tapestry5.FieldValidator;
20 import org.apache.tapestry5.Validator;
21 import org.apache.tapestry5.ioc.MessageFormatter;
22 import org.apache.tapestry5.ioc.Messages;
23 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
24 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
25 import org.apache.tapestry5.ioc.services.TypeCoercer;
26 import org.apache.tapestry5.runtime.Component;
27 import org.apache.tapestry5.services.FieldValidatorSource;
28 import org.apache.tapestry5.services.FormSupport;
29 import org.apache.tapestry5.validator.ValidatorMacro;
30
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34
35 import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newList;
36
37 @SuppressWarnings("all")
38 public class FieldValidatorSourceImpl implements FieldValidatorSource
39 {
40 private final Messages globalMessages;
41
42 private final Map<String, Validator> validators;
43
44 private final TypeCoercer typeCoercer;
45
46 private final FormSupport formSupport;
47
48 private final ValidatorMacro validatorMacro;
49
50 public FieldValidatorSourceImpl(Messages globalMessages, TypeCoercer typeCoercer,
51 FormSupport formSupport, Map<String, Validator> validators, ValidatorMacro validatorMacro)
52 {
53 this.globalMessages = globalMessages;
54 this.typeCoercer = typeCoercer;
55 this.formSupport = formSupport;
56 this.validators = validators;
57 this.validatorMacro = validatorMacro;
58 }
59
60 public FieldValidator createValidator(Field field, String validatorType, String constraintValue)
61 {
62 Component component = (Component) field;
63 assert InternalUtils.isNonBlank(validatorType);
64 ComponentResources componentResources = component.getComponentResources();
65 String overrideId = componentResources.getId();
66
67
68
69
70 Messages overrideMessages = componentResources.getContainerMessages();
71
72 return createValidator(field, validatorType, constraintValue, overrideId, overrideMessages, null);
73 }
74
75 public FieldValidator createValidator(Field field, String validatorType, String constraintValue, String overrideId,
76 Messages overrideMessages, Locale locale)
77 {
78
79 ValidatorSpecification originalSpec = new ValidatorSpecification(validatorType, constraintValue);
80
81 List<ValidatorSpecification> org = CollectionFactory.newList(originalSpec);
82
83 List<ValidatorSpecification> specs = expandMacros(org);
84
85 List<FieldValidator> fieldValidators = CollectionFactory.<FieldValidator>newList();
86
87 for (ValidatorSpecification spec : specs)
88 {
89 fieldValidators.add(createValidator(field, spec, overrideId, overrideMessages));
90 }
91
92 return new CompositeFieldValidator(fieldValidators);
93 }
94
95 private FieldValidator createValidator(Field field, ValidatorSpecification spec, String overrideId,
96 Messages overrideMessages)
97 {
98
99 String validatorType = spec.getValidatorType();
100
101 assert InternalUtils.isNonBlank(validatorType);
102 Validator validator = validators.get(validatorType);
103
104 if (validator == null)
105 throw new IllegalArgumentException(String.format("Unknown validator type '%s'. Configured validators are %s.", validatorType, InternalUtils.join(InternalUtils.sortedKeys(validators))));
106
107
108
109
110 String formValidationid = formSupport.getFormValidationId();
111
112 Object coercedConstraintValue = computeConstraintValue(validatorType, validator, spec.getConstraintValue(),
113 formValidationid, overrideId, overrideMessages);
114
115 MessageFormatter formatter = findMessageFormatter(formValidationid, overrideId, overrideMessages, validatorType,
116 validator);
117
118 return new FieldValidatorImpl(field, coercedConstraintValue, formatter, validator, formSupport);
119 }
120
121 private Object computeConstraintValue(String validatorType, Validator validator, String constraintValue,
122 String formId, String overrideId, Messages overrideMessages)
123 {
124 Class constraintType = validator.getConstraintType();
125
126 String constraintText = findConstraintValue(validatorType, constraintType, constraintValue, formId, overrideId,
127 overrideMessages);
128
129 if (constraintText == null)
130 return null;
131
132 return typeCoercer.coerce(constraintText, constraintType);
133 }
134
135 private String findConstraintValue(String validatorType, Class constraintType, String constraintValue,
136 String formValidationId, String overrideId, Messages overrideMessages)
137 {
138 if (constraintValue != null)
139 return constraintValue;
140
141 if (constraintType == null)
142 return null;
143
144
145
146
147 String perFormKey = formValidationId + "-" + overrideId + "-" + validatorType;
148
149 if (overrideMessages.contains(perFormKey))
150 return overrideMessages.get(perFormKey);
151
152 String generalKey = overrideId + "-" + validatorType;
153
154 if (overrideMessages.contains(generalKey))
155 return overrideMessages.get(generalKey);
156
157 throw new IllegalArgumentException(String.format("Validator '%s' requires a validation constraint (of type %s) but none was provided. The constraint may be provided inside the @Validator annotation on the property, or in the associated component message catalog as key '%s' or key '%s'.", validatorType, constraintType.getName(), perFormKey,
158 generalKey));
159 }
160
161 private MessageFormatter findMessageFormatter(String formId, String overrideId, Messages overrideMessages,
162 String validatorType, Validator validator)
163 {
164
165 String overrideKey = formId + "-" + overrideId + "-" + validatorType + "-message";
166
167 if (overrideMessages.contains(overrideKey))
168 return overrideMessages.getFormatter(overrideKey);
169
170 overrideKey = overrideId + "-" + validatorType + "-message";
171
172 if (overrideMessages.contains(overrideKey))
173 return overrideMessages.getFormatter(overrideKey);
174
175 String key = validator.getMessageKey();
176
177 return globalMessages.getFormatter(key);
178 }
179
180 public FieldValidator createValidators(Field field, String specification)
181 {
182 List<ValidatorSpecification> specs = toValidatorSpecifications(specification);
183
184 List<FieldValidator> fieldValidators = CollectionFactory.newList();
185
186 for (ValidatorSpecification spec : specs)
187 {
188 fieldValidators.add(createValidator(field, spec.getValidatorType(), spec.getConstraintValue()));
189 }
190
191 if (fieldValidators.size() == 1)
192 return fieldValidators.get(0);
193
194 return new CompositeFieldValidator(fieldValidators);
195 }
196
197 List<ValidatorSpecification> toValidatorSpecifications(String specification)
198 {
199 return expandMacros(parse(specification));
200 }
201
202 private List<ValidatorSpecification> expandMacros(List<ValidatorSpecification> specs)
203 {
204 Map<String, Boolean> expandedMacros = CollectionFactory.newCaseInsensitiveMap();
205 List<ValidatorSpecification> queue = CollectionFactory.newList(specs);
206 List<ValidatorSpecification> result = CollectionFactory.newList();
207
208 while (!queue.isEmpty())
209 {
210 ValidatorSpecification head = queue.remove(0);
211
212 String validatorType = head.getValidatorType();
213
214 String expanded = validatorMacro.valueForMacro(validatorType);
215 if (expanded != null)
216 {
217 if (head.getConstraintValue() != null)
218 throw new RuntimeException(String.format(
219 "'%s' is a validator macro, not a validator, and can not have a constraint value.",
220 validatorType));
221
222 if (expandedMacros.containsKey(validatorType))
223 throw new RuntimeException(String.format("Validator macro '%s' appears more than once.",
224 validatorType));
225
226 expandedMacros.put(validatorType, true);
227
228 List<ValidatorSpecification> parsed = parse(expanded);
229
230
231
232 for (int i = 0; i < parsed.size(); i++)
233 {
234 queue.add(i, parsed.get(i));
235 }
236 } else
237 {
238 result.add(head);
239 }
240 }
241
242 return result;
243 }
244
245
246
247
248 enum State
249 {
250
251
252
253
254 TYPE_START,
255
256
257
258 TYPE_END,
259
260
261
262 EQUALS_OR_COMMA,
263
264
265
266 VALUE_START,
267
268
269
270 VALUE_END,
271
272
273
274 COMMA
275 }
276
277 static List<ValidatorSpecification> parse(String specification)
278 {
279 List<ValidatorSpecification> result = newList();
280
281 char[] input = specification.toCharArray();
282
283 int cursor = 0;
284 int start = -1;
285
286 String type = null;
287 boolean skipWhitespace = true;
288 State state = State.TYPE_START;
289
290 while (cursor < input.length)
291 {
292 char ch = input[cursor];
293
294 if (skipWhitespace && Character.isWhitespace(ch))
295 {
296 cursor++;
297 continue;
298 }
299
300 skipWhitespace = false;
301
302 switch (state)
303 {
304
305 case TYPE_START:
306
307 if (Character.isLetter(ch))
308 {
309 start = cursor;
310 state = State.TYPE_END;
311 break;
312 }
313
314 parseError(cursor, specification);
315
316 case TYPE_END:
317
318 if (Character.isLetter(ch))
319 {
320 break;
321 }
322
323 type = specification.substring(start, cursor);
324
325 skipWhitespace = true;
326 state = State.EQUALS_OR_COMMA;
327 continue;
328
329 case EQUALS_OR_COMMA:
330
331 if (ch == '=')
332 {
333 skipWhitespace = true;
334 state = State.VALUE_START;
335 break;
336 }
337
338 if (ch == ',')
339 {
340 result.add(new ValidatorSpecification(type));
341 type = null;
342 state = State.COMMA;
343 continue;
344 }
345
346 parseError(cursor, specification);
347
348 case VALUE_START:
349
350 start = cursor;
351 state = State.VALUE_END;
352 break;
353
354 case VALUE_END:
355
356
357
358 if (Character.isWhitespace(ch) || ch == ',')
359 {
360 String value = specification.substring(start, cursor);
361
362 result.add(new ValidatorSpecification(type, value));
363 type = null;
364
365 skipWhitespace = true;
366 state = State.COMMA;
367 continue;
368 }
369
370 break;
371
372 case COMMA:
373
374 if (ch == ',')
375 {
376 skipWhitespace = true;
377 state = State.TYPE_START;
378 break;
379 }
380
381 parseError(cursor, specification);
382 }
383
384 cursor++;
385 }
386
387
388
389
390 switch (state)
391 {
392 case TYPE_END:
393
394 type = specification.substring(start);
395
396 case EQUALS_OR_COMMA:
397
398 result.add(new ValidatorSpecification(type));
399 break;
400
401
402
403 case VALUE_START:
404 result.add(new ValidatorSpecification(type, ""));
405 break;
406
407 case VALUE_END:
408
409 result.add(new ValidatorSpecification(type, specification.substring(start)));
410 break;
411
412
413
414 default:
415 }
416
417 return result;
418 }
419
420 private static void parseError(int cursor, String specification)
421 {
422 throw new RuntimeException(String.format("Unexpected character '%s' at position %d of input string: %s", specification.charAt(cursor), cursor + 1,
423 specification));
424 }
425 }